package com.sprite.utils;

/**
 * <p>描述：分布式自增长ID</p>
 * <pre>
 *     Twitter的 SnowFlake　JAVA实现方案
 * </pre>
 * 0---0000000000 0000000000 0000000000 0000000000 0 --- 00000 ---00000 ---000000000000
 * 64位ID (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加))
 *
 * @author Jack
 */
public final class SnowflakeWorker {

    /**
     * 基准时间，恒定不变，但不能大于当前时间，默认2019 年1月1日 00:00:00
     */
    private final static long defaultBaseTime = 1546272000000L;

    /**
     * 机器Id位数
     */
    private final static int defaultWorkerIdBits = 5;

    /**
     * 业务Id 位数
     */
    private final static int defaultDatacenterIdBits = 5;

    /**
     * 毫秒内自增位数
     */
    private final static int defaultSequnceBits = 12;

    private long baseTime;
    private int workerIdBits;
    private int datacenterIdBits;
    private int sequnceBits;

    private long lastTime;
    private long sequence;
    private long sequenceMask;

    private long datacenterId;
    private long workerId;

    private int workerIdLeftShift;
    private int datacenterIdLeftShift;

    private int timeSpanLeftShift;

    private SnowflakeWorker(long baseTime, int workerIdBits, int datacenterIdBits, int sequnceBits, long workerId, long datacenterId) {
        this.baseTime = baseTime;
        this.workerIdBits = workerIdBits;
        this.datacenterIdBits = datacenterIdBits;
        this.sequnceBits = sequnceBits;
        this.lastTime = baseTime;
        this.sequenceMask = -1L ^ (-1L << sequnceBits);
        this.datacenterId = datacenterId;
        this.workerId = workerId;
        this.workerIdLeftShift = sequnceBits + datacenterIdBits;
        this.datacenterIdLeftShift = sequnceBits;
        this.timeSpanLeftShift = sequnceBits + datacenterIdBits + workerIdBits;
        check();
    }

    private SnowflakeWorker(long baseTime, long workerId, long datacenterId) {
        this(baseTime, defaultWorkerIdBits, defaultDatacenterIdBits, defaultSequnceBits, workerId, datacenterId);
    }

    private SnowflakeWorker(long workerId, long datacenterId) {
        this(defaultBaseTime, defaultWorkerIdBits, defaultDatacenterIdBits, defaultSequnceBits, workerId, datacenterId);
    }

    private void check() {
        if (baseTime > System.currentTimeMillis()) {
            throw new IllegalArgumentException("baseTime must letter than current time");
        }
        if (workerIdBits + datacenterIdBits + sequnceBits > 22) {
            throw new IllegalArgumentException("workerIdBits + datacenterIdBits + sequnceBits, bits must less than 22");
        }

        if ((workerId >> workerIdBits) != 0) {
            throw new IllegalArgumentException("workerId bits error");
        }

        if ((datacenterId >> datacenterIdBits) != 0) {
            throw new IllegalArgumentException("datacenterId bits error");
        }
    }

    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();
        if (timestamp < lastTime || timestamp < baseTime) {
            throw new RuntimeException(UtilString.place("refuse generate id, baseTime:{}, timestamp:{}, lastTime{}", baseTime, timestamp, lastTime));
        }

        if (lastTime == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                while ((timestamp = System.currentTimeMillis()) <= lastTime) {

                }
            }
        } else {
            sequence = 0L;
        }

        lastTime = timestamp;
        return (timestamp - baseTime) << this.timeSpanLeftShift | (workerId << workerIdLeftShift) | (datacenterId << datacenterIdLeftShift) | sequence;
    }

    public static class Builder {
        private long baseTime = defaultBaseTime;
        private int workerIdBits = defaultWorkerIdBits;
        private int datacenterIdBits = defaultDatacenterIdBits;
        private int sequenceBits = defaultSequnceBits;
        private long workerId;
        private long datacenterId;

        public Builder baseTime(long baseTime) {
            this.baseTime = baseTime;
            return this;
        }

        public Builder workerIdBits(int workerIdBits) {
            this.workerIdBits = workerIdBits;
            return this;
        }

        public Builder datacenterIdBits(int datacenterIdBits) {
            this.datacenterIdBits = datacenterIdBits;
            return this;
        }

        public Builder sequenceBits(int sequnceBits) {
            this.sequenceBits = sequnceBits;
            return this;
        }

        public Builder workerId(int workerId) {
            this.workerId = workerId;
            return this;
        }

        public Builder datacenterId(int datacenterId) {
            this.datacenterId = datacenterId;
            return this;
        }

        public SnowflakeWorker build() {
            return new SnowflakeWorker(this.baseTime, this.workerIdBits, this.datacenterIdBits, this.sequenceBits, this.workerId, this.datacenterId);
        }
    }

    public static void main(String[] args) {
        System.out.println(System.currentTimeMillis() - 1546272000000L);
    }
}
